package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/douglarek/zerodown"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

type JWTClaims struct { // token里面添加用户信息，验证token后可能会用到用户信息
	jwt.StandardClaims
	UserID      int      `json:"user_id"`
	Password    string   `json:"password"`
	Username    string   `json:"username"`
	FullName    string   `json:"full_name"`
	Permissions []string `json:"permissions"`
}

var (
	Secret     = "dong_tech" // 加盐
	ExpireTime = 3600        // token有效期
)

func login(c *gin.Context) {
	username := c.Param("username")
	password := c.Param("password")
	claims := &JWTClaims{
		UserID:      1,
		Username:    username,
		Password:    password,
		FullName:    username,
		Permissions: []string{},
	}
	claims.IssuedAt = time.Now().Unix()
	claims.ExpiresAt = time.Now().Add(time.Second * time.Duration(ExpireTime)).Unix()
	signedToken, err := getToken(claims)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return
	}
	c.String(http.StatusOK, signedToken)
}

func refresh(c *gin.Context) {
	strToken := c.Param("token")
	claims, err := verifyAction(strToken)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return
	}
	claims.ExpiresAt = time.Now().Unix() + (claims.ExpiresAt - claims.IssuedAt)
	signedToken, err := getToken(claims)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return
	}
	c.String(http.StatusOK, signedToken)
}

const (
	ErrorReason_ServerBusy = "服务器繁忙"
	ErrorReason_ReLogin    = "请重新登陆"
)

func verifyAction(strToken string) (*JWTClaims, error) {
	token, err := jwt.ParseWithClaims(strToken, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(Secret), nil
	})
	if err != nil {
		return nil, errors.New(ErrorReason_ServerBusy)
	}
	claims, ok := token.Claims.(*JWTClaims)
	if !ok {
		return nil, errors.New(ErrorReason_ReLogin)
	}
	if err := token.Claims.Valid(); err != nil {
		return nil, errors.New(ErrorReason_ReLogin)
	}
	fmt.Println("verify")
	return claims, nil
}

func getToken(claims *JWTClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	signedToken, err := token.SignedString([]byte(Secret))
	if err != nil {
		return "", errors.New(ErrorReason_ServerBusy)
	}
	return signedToken, nil
}

func verify(c *gin.Context) gin.H {
	strToken := c.Request.Header["Authorization"][0]
	claim, err := verifyAction(strToken)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return gin.H{
			"success": false,
			"msg":     err.Error(),
		}
	}
	return gin.H{
		"success":  true,
		"username": claim.Username,
		"msg":      "",
	}
}

type LogInfo struct {
	TimeStamp string `json:"@timeStamp,string"`
	AppName   string `json:"appName,string"`
	Event     string `json:"event,string"`
	Level     string `json:"level,string"`
	Msg       string `json:"msg,string"`
}

// Strval 获取变量的字符串值
// 浮点型 3.0将会转换成字符串3, "3"
// 非数值或字符类型的变量将会被转换成JSON格式字符串
func Strval(value interface{}) string {
	// interface 转 string
	var key string
	if value == nil {
		return key
	}

	switch value.(type) {
	case float64:
		ft := value.(float64)
		key = strconv.FormatFloat(ft, 'f', -1, 64)
	case float32:
		ft := value.(float32)
		key = strconv.FormatFloat(float64(ft), 'f', -1, 64)
	case int:
		it := value.(int)
		key = strconv.Itoa(it)
	case uint:
		it := value.(uint)
		key = strconv.Itoa(int(it))
	case int8:
		it := value.(int8)
		key = strconv.Itoa(int(it))
	case uint8:
		it := value.(uint8)
		key = strconv.Itoa(int(it))
	case int16:
		it := value.(int16)
		key = strconv.Itoa(int(it))
	case uint16:
		it := value.(uint16)
		key = strconv.Itoa(int(it))
	case int32:
		it := value.(int32)
		key = strconv.Itoa(int(it))
	case uint32:
		it := value.(uint32)
		key = strconv.Itoa(int(it))
	case int64:
		it := value.(int64)
		key = strconv.FormatInt(it, 10)
	case uint64:
		it := value.(uint64)
		key = strconv.FormatUint(it, 10)
	case string:
		key = value.(string)
	case []byte:
		key = string(value.([]byte))
	default:
		newValue, _ := json.Marshal(value)
		key = string(newValue)
	}

	return key
} // 错误处理的结构体
type Error struct {
	StatusCode int    `json:"-"`
	Code       int    `json:"code"`
	Msg        string `json:"msg"`
}

var (
	Success     = NewError(http.StatusOK, 0, "success")
	ServerError = NewError(http.StatusInternalServerError, 200500, "系统异常，请稍后重试!")
	NotFound    = NewError(http.StatusNotFound, 200404, http.StatusText(http.StatusNotFound))
)

func OtherError(message string) *Error {
	return NewError(http.StatusForbidden, 100403, message)
}

func (e *Error) Error() string {
	return e.Msg
}

func NewError(statusCode, Code int, msg string) *Error {
	return &Error{
		StatusCode: statusCode,
		Code:       Code,
		Msg:        msg,
	}
}

// 404处理
func HandleNotFound(c *gin.Context) {
	err := NotFound
	c.JSON(err.StatusCode, err)
	return
}

func ErrHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next()
		if length := len(c.Errors); length > 0 {
			e := c.Errors[length-1]
			err := e.Err
			if err != nil {
				var Err *Error
				if e, ok := err.(*Error); ok {
					Err = e
				} else if e, ok := err.(error); ok {
					Err = OtherError(e.Error())
				} else {
					Err = ServerError
				}
				// 记录一个错误的日志
				c.JSON(Err.StatusCode, Err)
				return
			}
		}

	}
}

func interfaceToArr(obj interface{}) []map[string]interface{} {
	var list []map[string]interface{}
	if reflect.TypeOf(obj).Kind() == reflect.Slice {
		s := reflect.ValueOf(obj)
		for i := 0; i < s.Len(); i++ {
			ele := s.Index(i)
			list = append(list, ele.Interface().(map[string]interface{}))
		}
	}
	return list
}

func logHandler(c *gin.Context) {
	jsonPostIn := make(map[string]interface{}) //注意该结构接受的内容
	c.BindJSON(&jsonPostIn)
	log.Printf("%v", &jsonPostIn)
	isMulti := jsonPostIn["isMulti"]
	jsonArr := make([]map[string]interface{}, 0)
	if isMulti == nil || isMulti == "" && isMulti == "0" {
		jsonArr = append(jsonArr, jsonPostIn)
	} else {
		jsonArr = interfaceToArr(jsonPostIn["data"])
	}

	jsonResArr := make([]map[string]interface{}, 0)

	for _, jsonPost := range jsonArr {
		appName := jsonPost["appName"]
		timeStamp := jsonPost["time"]
		event := jsonPost["event"]
		msg := jsonPost["msg"]
		level := jsonPost["level"]
		jsonObj := make(map[string]interface{})
		var l LogInfo
		if timeStamp != nil {
			l.TimeStamp = Strval(timeStamp)
		} else {
			timeStr := time.Now().Format("2006-01-02T15:04:05.000Z")
			l.TimeStamp = timeStr
		}
		jsonObj["@timestamp"] = l.TimeStamp
		var logPath string
		if appName != nil {
			l.AppName = Strval(appName)
		} else {
			l.AppName = "v-app"
		}
		logPath = "logs-" + l.AppName + "-default"
		if event != nil {
			l.Event = Strval(event)
		} else {
			l.Event = "V-EVENT"
		}
		if msg != nil {
			l.Msg = Strval(msg)
		} else {
			l.Msg = "[[empty]]"
		}
		if level != nil {
			if level != "warn" && level != "info" && level != "error" && level != "debug" {
				level = "info"
			}
			l.Level = Strval(level)
		} else {
			l.Level = "info"
		}
		jsonObj["event"] = l.Event
		jsonObj["appName"] = l.AppName
		jsonObj["level"] = l.Level
		jsonObj["msg"] = l.Msg

		jsonB, err := json.Marshal(jsonObj)

		b, err := json.Marshal(l)
		if err != nil {
			log.Println("JSON ERR:", err)
			//c.JSON(500, gin.H{"message": err})
			jsonResArr = append(jsonResArr, gin.H{"message": err})
			continue
		}
		b = jsonB
		log.Println(string(b))
		url := "http://" + Strval(conf["esServer"]) + "/" + logPath + "/_doc"
		log.Println(url)
		client := &http.Client{}
		reqest, err := http.NewRequest("POST", url, strings.NewReader(string(b)))
		if err != nil {
			log.Fatalln(err)
			//c.JSON(500, gin.H{"message": err})
			jsonResArr = append(jsonResArr, gin.H{"message": err})
			continue
		}
		reqest.Header.Set("Content-Type", "application/json; charset=utf-8")

		//处理返回结果
		response, err := client.Do(reqest)
		if err != nil {
			log.Fatalln(err)
			//c.JSON(500, gin.H{"message": err})
			jsonResArr = append(jsonResArr, gin.H{"message": err})
			continue
		}

		//将结果定位到标准输出 也可以直接打印出来 或者定位到其他地方进行相应的处理
		// stdout := os.Stdout
		// _, err = io.Copy(stdout, response.Body)

		// //返回的状态码
		// status := response.StatusCode

		// fmt.Println(status)

		defer response.Body.Close() //ok, most of the time :-)
		body, err := ioutil.ReadAll(response.Body)
		if err != nil {
			log.Fatalln(err)
			//c.JSON(500, gin.H{"message": err})
			jsonResArr = append(jsonResArr, gin.H{"message": err})
			continue
		}

		log.Println(string(body))
		jsonRes := make(map[string]interface{})
		err = json.Unmarshal([]byte(body), &jsonRes)
		if err != nil {
			log.Fatalln(err)
			//c.JSON(500, gin.H{"message": err})
			jsonResArr = append(jsonResArr, gin.H{"message": err})
			continue
		}
		jsonResArr = append(jsonResArr, jsonRes)
	}
	c.JSON(200, jsonResArr)
}

func logViewHandler(c *gin.Context) {
	level := c.Query("level")
	appName := c.Query("appName")
	event := c.Query("event")
	dt := c.Query("startDateTime")
	et := c.Query("endDateTime")
	page := c.Query("page")
	size := c.Query("size")
	desc := c.Query("desc")
	isReg := c.Query("isReg")
	ex := c.Query("exclude")

	searchJson := make(map[string]interface{})
	queryJson := make(map[string]interface{})
	boolJson := make(map[string]interface{})
	mustJson := make([]map[string]interface{}, 0)
	mustNotJson := make([]map[string]interface{}, 0)
	rangeJson := make(map[string]interface{})
	timeJson := make(map[string]interface{})
	if level == "" || (level != "warn" && level != "info" && level != "error" && level != "debug") {
		level = ""
	}
	if desc == "" || (desc != "desc" && desc != "acs") {
		desc = "desc"
	}
	if isReg == "" || (isReg != "0" && isReg != "1") {
		isReg = "0"
	}
	exList := make([]string, 0)
	if ex != "" {
		if strings.Index(ex, "||") != -1 {
			exList = strings.Split(ex, "||")
		} else {
			exList = append(exList, ex)
		}
	}
	var pageNum int64 = 0
	if page == "" {
		pageNum = 0
	} else {
		pageNum, _ = strconv.ParseInt(page, 0, 64)
	}
	var sizeNum int64 = 0
	if size == "" {
		sizeNum = 1000
	} else {
		sizeNum, _ = strconv.ParseInt(size, 0, 64)
	}
	if dt == "" {
		dt = time.Now().Format("2006-01-02") + "T00:00:00"
	}
	if et == "" {
		et = time.Now().Format("2006-01-02T15:04:05")
	}
	t1, err := time.Parse("2006-01-02T15:04:05", dt)
	t2, err := time.Parse("2006-01-02T15:04:05", et)
	if err == nil && t1.After(t2) {
		c.HTML(500, "log.tmpl", gin.H{
			"code":    500,
			"message": "startDateTime exceeds the current time"})
		return
	}
	m := ""
	var an string
	var matchTag = ""
	if isReg == "1" {
		matchTag = "regexp"
	} else {
		matchTag = "wildcard"
	}
	if appName != "" {
		if isReg == "0" {
			appName = "*" + appName + "*"
		}
		matchJson := make(map[string]interface{})
		appNameJson := make(map[string]interface{})
		appNameJson["appName"] = appName
		matchJson[matchTag] = appNameJson
		mustJson = append(mustJson, matchJson)
		an = `,
		{
			"` + matchTag + `":{
				"appName":"` + appName + `"
			}
		}`
	} else {
		an = ""
	}
	var e string
	if event != "" {
		if isReg == "0" {
			event = "*" + event + "*"
		}
		matchJson := make(map[string]interface{})
		eventJson := make(map[string]interface{})
		eventJson["event"] = event
		matchJson[matchTag] = eventJson
		mustJson = append(mustJson, matchJson)
		e = `,
		{
			"` + matchTag + `":{
				"event":"` + event + `"
			}
		}`
	} else {
		e = ""
	}
	var l string
	if level != "" {
		if isReg == "0" {
			level = "*" + level + "*"
		}
		matchJson := make(map[string]interface{})
		levelJson := make(map[string]interface{})
		levelJson["level"] = level
		matchJson[matchTag] = levelJson
		mustJson = append(mustJson, matchJson)
		l = `,
		{
			"` + matchTag + `":{
				"level":"` + level + `"
			}
		}`
	} else {
		l = ""
	}

	if an+e+l != "" {
		m = an + e + l
	}
	exclude := conf["exclude"].([]interface{})
	for _, exStr := range exList {
		exclude = append(exclude, exStr)
	}
	mn := ""
	excludeLen := len(exclude)
	reqList := []map[string]string{{"symbol": "$"}, {"symbol": "["}, {"symbol": "]"}, {"symbol": "{"}, {"symbol": "}"}, {"symbol": "("}, {"symbol": ")"}, {"symbol": "*"}, {"symbol": "+"}, {"symbol": "."}, {"symbol": "?"}, {"symbol": "\\", "escape": "\\\\\\\\"}, {"symbol": "^"}, {"symbol": "|"}}
	if excludeLen > 0 {
		regexpStr := ""
		for _, excludeLog := range exclude {
			repRes := Strval(excludeLog)
			for _, reqStr := range reqList {
				symbol := reqStr["symbol"]
				if strings.Index(repRes, symbol) != -1 {
					escape := reqStr["escape"]
					if escape == "" {
						repRes = strings.ReplaceAll(repRes, symbol, "\\\\"+symbol)
					} else {
						repRes = strings.ReplaceAll(repRes, symbol, "\\\\"+escape)
					}
				}
			}
			matchJson := make(map[string]interface{})
			msgJson := make(map[string]interface{})
			msgJson["msg"] = `.*` + repRes + `.*`
			matchJson["regexp"] = msgJson
			mustNotJson = append(mustNotJson, matchJson)
			regexpStrTmp := `{
                    "regexp": {
                        "msg": ".*` + repRes + `.*"
                    }
                }`
			if regexpStr != "" {
				regexpStr += ","
			}
			regexpStr += regexpStrTmp
		}
		if regexpStr != "" {
			mn = `,
            
            "must_not": [
                ` + regexpStr + `
            ]
`
		}
	}

	//url := "http://localhost:9200/_search?size=10000"
	//var q string
	//if appName != "" {
	//	q = "appName:" + appName
	//}
	//if event != "" {
	//	if q != "" {
	//		q += "+"
	//	}
	//	q += "event:" + event
	//}
	//if q != "" {
	//	url += "&q=" + q
	//}
	//log.Println(url)
	//response, err := http.Get(url)
	//if err != nil {
	//	log.Fatalln(err)
	//	c.JSON(500, gin.H{"message": err})
	//	return
	//}
	//
	//defer response.Body.Close() //ok, most of the time :-)
	//body, err := ioutil.ReadAll(response.Body)
	//if err != nil {
	//	log.Fatalln(err)
	//	c.JSON(500, gin.H{"message": err})
	//	return
	//}
	//
	//log.Println(string(body))

	url := "http://" + Strval(conf["esServer"]) + "/_search?size=" + strconv.FormatInt(sizeNum, 10) + "&from=" + strconv.FormatInt(pageNum*sizeNum, 10)
	log.Println(url)
	method := "GET"

	timeJson["gte"] = strconv.FormatInt(t1.UnixNano()/1e6, 10)
	timeJson["lte"] = strconv.FormatInt(t2.UnixNano()/1e6, 10)
	rangeJson["@timestamp"] = timeJson
	rangeObjJson := make(map[string]interface{})
	rangeObjJson["range"] = rangeJson
	mustJson = append(mustJson, rangeObjJson)
	boolJson["must"] = mustJson
	boolJson["must_not"] = mustNotJson
	queryJson["bool"] = boolJson
	searchJson["query"] = queryJson
	sortJson := make([]map[string]interface{}, 0)
	timeStampJson := make(map[string]interface{})
	timeStampJson["@timestamp"] = desc
	sortJson = append(sortJson, timeStampJson)
	searchJson["sort"] = sortJson

	log.Println(searchJson)

	searchStr, _ := json.Marshal(searchJson)

	queryStr := `{
										"query": {
											"bool": {
												"must": [
													{
														"range": {
															"@timestamp": {
																"gte": "` + strconv.FormatInt(t1.UnixNano()/1e6, 10) + `",
																"lte": "` + strconv.FormatInt(t2.UnixNano()/1e6, 10) + `"
															}
														}
													}
													` + m + `
												]
													` + mn + `
											}
										},
										"sort": [
													{
														"@timestamp": "` + desc + `"
													}
												]
									}`
	log.Println(queryStr)

	queryStr = string(searchStr)
	log.Println(queryStr)
	payload := strings.NewReader(queryStr)

	client := &http.Client{}
	req, err := http.NewRequest(method, url, payload)

	if err != nil {
		fmt.Println(err)
		c.HTML(500, "log.tmpl", gin.H{
			"code":    500,
			"message": err})
		return
	}
	req.Header.Add("Content-Type", "application/json")

	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		c.HTML(500, "log.tmpl", gin.H{
			"code":    500,
			"message": err})
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		c.HTML(500, "log.tmpl", gin.H{
			"code":    500,
			"message": err})
		return
	}
	fmt.Println(string(body))

	jsonRes := make(map[string]interface{})
	err = json.Unmarshal([]byte(body), &jsonRes)
	if err != nil {
		log.Fatalln(err)
		c.HTML(500, "log.tmpl", gin.H{
			"code":    500,
			"message": err})
		return
	}

	//logHtml := ""
	//hits := jsonRes["hits"].(map[string]interface{})["hits"].([]interface{})
	//msgInfoList := make([]string, 0)
	//for _, hit := range hits {
	//	hitInfo := hit.(map[string]interface{})
	//	_source := hitInfo["_source"].(map[string]interface{})
	//	timestamp := Strval(_source["@timestamp"])
	//	level := Strval(_source["level"])
	//	appName := Strval(_source["appName"])
	//	event := Strval(_source["event"])
	//	msg := Strval(_source["msg"])
	//	msgInfoList = append(msgInfoList, timestamp+" ["+appName+"] ["+event+"] ["+level+"] "+msg)
	//}
	//log.Println(msgInfoList)

	c.HTML(200, "log.tmpl", gin.H{
		"code": 200,
		"body": string(body),
		//"logHtml": logHtml,
		"exclude": Strval(conf["exclude"]),
	})
}

/**
 * 判断文件是否存在  存在返回 true 不存在返回false
 */
func checkFileIsExist(filename string) bool {
	var exist = true
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		exist = false
	}
	return exist
}

var conf = make(map[string]interface{})

func getConf() {
	filename := "conf.json"
	var f *os.File
	var err1 error
	if checkFileIsExist(filename) { //如果文件存在
		log.Println("配置文件存在")
	} else {
		log.Println("配置文件不存在,创建并初始化配置文件")
		f, err1 = os.Create(filename) //创建文件
		_, err1 = io.WriteString(f,
			`{
	"ip": "0.0.0.0",
	"port": 9222,
	"esServer": "localhost:9200",
	"exclude": [
		"刷新排队人数,等待时间",
		"waitingNumber===",
		"获取在线空闲客服",
		"排队计时",
		"取消刷新等待人数",
		"[ShakeHand]"
		]
}`)
	}
	if err1 != nil {
		panic(err1)
	}
	// 打开文件
	file, err := os.Open("conf.json")
	if err != nil {
		panic(err1)
	}

	// 关闭文件
	defer file.Close()

	//NewDecoder创建一个从file读取并解码json对象的*Decoder，解码器有自己的缓冲，并可能超前读取部分json数据。
	decoder := json.NewDecoder(file)

	//Decode从输入流读取下一个json编码值并保存在v指向的值里
	err = decoder.Decode(&conf)
	if err != nil {
		fmt.Println("Error:", err)
	}
}

func updateConfHandler(c *gin.Context) {
	getConf()
	c.JSON(200, gin.H{
		"code":    200,
		"message": "更新成功",
	})
}

func main() {
	getConf()
	ip := Strval(conf["ip"])
	port := Strval(conf["port"])
	log.Println(ip + ":" + port)
	r := gin.Default()
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"*"},
		AllowMethods:     []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"},
		AllowHeaders:     []string{"Origin", "Content-Type", "AccessToken", "X-CSRF-Token", "Authorization", "Token"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		//AllowOriginFunc: func(origin string) bool {
		//	return origin == "https://github.com"
		//},
		MaxAge: 12 * time.Hour,
	}))
	r.StaticFS("/public", http.Dir("./static"))
	r.NoMethod(HandleNotFound)
	r.NoRoute(HandleNotFound)
	r.Use(ErrHandler())
	r.POST("/log", logHandler)
	r.LoadHTMLGlob("./webapp/*")
	r.GET("/log", logViewHandler)
	r.GET("/updateConf", updateConfHandler)
	// r.Run(ip + ":" + port) // listen and serve on 0.0.0.0:8080
	log.Fatalln(zerodown.ListenAndServe(ip+":"+port, r))
}
